/*******************************************************
版本描述:该版本为了实现先接收数据，再发送数据的顺序，新增了信号量;
同时为了避免使用全局变量，把信号量等公共变量归纳进线程处理函数参数中了。
主框架描述:
    int main(int argc, char *argv[])
    {
        // 1、创建原始套接字
        // 2、初始化公共变量（信号量、套接字）
        // 3、创建并填充一个公共参数
        // 4、创建线程1：接收数据，参数传递
        // 5、创建线程2：发送数据，参数传递
        return 0;
    }
    线程1(接收数据):
        // 1、接收数据
        // 2、sem_post
    线程2(发送数据):
        // 1、sem_wait
        // 2、发送数据
编译方法:gcc -g -pthread sockraw.c
运行方法:sudo ./a.out  (运行后再ping该主机所在IP)
*******************************************************/
#include <pcap.h>

#include <net/if.h>// struct ifreq
#include <sys/ioctl.h> // ioctl、SIOCGIFADDR
#include <sys/socket.h> // socket
#include <netinet/ether.h> // ETH_P_ALL
#include <netpacket/packet.h> // struct sockaddr_ll

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>

#define BUFLEN (1024)
#define THREADSNUM (2)

/* 为了尽量避免使用全局变量，因此新增多线程公用结构体 */
typedef struct tag_public_arg {
    sem_t pa_sem;//公用信号量
    int pa_fd;//公用套接字描述符
}public_arg_t;
typedef struct tag_private_arg {
    pthread_t pa_tid;
    int pa_ret;
    unsigned char pa_buf[BUFLEN];
}private_arg_t;

/* 线程参数结构体 */
typedef struct tag_thread_arg {
    public_arg_t ta_pub;
    private_arg_t ta_prv[THREADSNUM];//有多少线程就定义多少容量数组
}thread_arg_t;

/* red and flash print */
#define PRINT_POSITION_ERRNO(str,errorno) do { \
	fprintf(stderr,"\033[;31;5m%s:%s:%d:%s:%s\033\[0m\n", \
			__FILE__,__func__,__LINE__,\
			str,strerror(errorno)); } while(0);
#define PRINT_TO_LOGFILE(path) do { \
	fprintf(stderr,"\033[;31;5m%s:%s:%d:%s:%s\033\[0m\n", \
			__FILE__,__func__,__LINE__,\
			str,strerror(errorno)); } while(0);

void *thread_recv_handle(void *arg);
void *thread_send_handle(void *arg);

int main(int argc, char *argv[])
{
    unsigned int recv_len = 0;
    unsigned int send_len = 0;
    int sock_raw_fd = 0;
    unsigned char buf_recv[1024] = {0};
    unsigned char buf_send[1024] = {0};

    /* 01. create socket */
    //int socket ( int family, int type, int protocol );
    //family：协议族 这里写 PF_PACKET
    //type：  套接字类，这里写 SOCK_RAW
    //protocol：协议类别，指定可以接收或发送的数据包类型，不能写 “0”，取值如下，注意，传参时需要用 htons() 进行字节序转换。
    //ETH_P_IP：IPV4数据包
    //ETH_P_ARP：ARP数据包
    //ETH_P_ALL：任何协议类型的数据包
	sock_raw_fd = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
	if (sock_raw_fd < 0) {
		PRINT_POSITION_ERRNO("socket",errno);
		return errno;
	}

    /* 03. receive net packets from network */
    //sockfd:原始套接字
    //buf：接收数据缓冲区
    //nbytes:接收数据缓冲区的大小
    //flags：套接字标志(常为0)
    //from：这里没有用，写 NULL
    //addrlen：这里没有用，写 NULL

    /* 统一构建两个线程参数 */
    thread_arg_t arg = {0};
    arg.ta_pub.pa_fd = sock_raw_fd;
    sem_init(&arg.ta_pub.pa_sem,0,0);

    if(pthread_create(&arg.ta_prv[0].pa_tid,NULL,thread_recv_handle, (void *)&arg))
    {
        PRINT_POSITION_ERRNO("pthread_create",errno);
        close(sock_raw_fd);
        exit(-1);
    }

    /* 05. send packets to network */
    //sockfd：原始套接字
    //buf：发送数据缓冲区
    //nbytes:发送数据缓冲区的大小
    //flags：一般为 0
    //to：本机网络接口，指发送的数据应该从本机的哪个网卡出去，而不是以前的目的地址
    //addrlen：to 所指向内容的长度
    if(pthread_create(&arg.ta_prv[1].pa_tid,NULL,thread_send_handle, (void *)&arg))
    {
        PRINT_POSITION_ERRNO("pthread_create",errno);
        close(sock_raw_fd);
        exit(-1);
    }

    /* 等待线程结束 */
    pthread_join(arg.ta_prv[0].pa_tid, NULL);
    pthread_join(arg.ta_prv[1].pa_tid, NULL);
	return 0;
}

void *thread_recv_handle(void *arg)
{
    thread_arg_t *parg = (thread_arg_t *)arg;
    bool flag = true;

    /* 02. set misc mode */
    //通过命令给Linux网卡设置混杂模式：ifconfig eth0 promisc
    //通过命令给Linux网卡取消混杂模式：ifconfig eth0 -promisc
    //通过代码给Linux网卡设置混杂模式:
    struct ifreq ethreq = {0};
    strncpy(ethreq.ifr_name,"ens33",IFNAMSIZ);
    if (ioctl(parg->ta_pub.pa_fd,SIOCGIFFLAGS,&ethreq)) 
    {
        PRINT_POSITION_ERRNO("ioctl",errno);
        close(parg->ta_pub.pa_fd);
        exit(-1);
    }
    ethreq.ifr_flags |= IFF_PROMISC;
    if (ioctl(parg->ta_pub.pa_fd,SIOCSIFFLAGS,&ethreq))
    {
        PRINT_POSITION_ERRNO("ioctl",errno);
        close(parg->ta_pub.pa_fd);
        exit(-1);
    }
    while(flag) {
        memset(parg->ta_prv[0].pa_buf, 0, sizeof(parg->ta_prv[0].pa_buf));
        parg->ta_prv[0].pa_ret = recvfrom(parg->ta_pub.pa_fd, parg->ta_prv[0].pa_buf, sizeof(parg->ta_prv[0].pa_buf), 0, NULL, NULL);
        //printf(">>>> Recv Network Packet Successfully !!!\n");
        printf("MAC(6Bytes*2):%02x:%02x:%02x:%02x:%02x:%02x << %02x:%02x:%02x:%02x:%02x:%02x\n",
                parg->ta_prv[0].pa_buf[0],
                parg->ta_prv[0].pa_buf[1],
                parg->ta_prv[0].pa_buf[2],
                parg->ta_prv[0].pa_buf[3],
                parg->ta_prv[0].pa_buf[4],
                parg->ta_prv[0].pa_buf[5],
                parg->ta_prv[0].pa_buf[6],
                parg->ta_prv[0].pa_buf[7],
                parg->ta_prv[0].pa_buf[8],
                parg->ta_prv[0].pa_buf[9],
                parg->ta_prv[0].pa_buf[10],
                parg->ta_prv[0].pa_buf[11]);

        if (parg->ta_prv[0].pa_buf[6*2]==0x08 &&
                parg->ta_prv[0].pa_buf[6*2+1]==0x00)
        {
            printf("  Type(2Bytes):%#02x%02x (IP)\n",
                parg->ta_prv[0].pa_buf[6*2],
                parg->ta_prv[0].pa_buf[6*2+1]);
            printf("  Version(4bits): %d (IPv4~IPv6)\n", 
                    parg->ta_prv[0].pa_buf[6*2+2+0]>>4);
            printf("  IP Head Len(4bits): %d (20~69)\n", 
                    (parg->ta_prv[0].pa_buf[6*2+2+0]<<4)>>4);
            printf("  Protocol No: %d (1-ICMP 6-TCP 17-UDP)\n", 
                    parg->ta_prv[0].pa_buf[6*2+2+9]);
            printf("  IP domain: %d.%d.%d.%d >> %d.%d.%d.%d\n",
                    parg->ta_prv[0].pa_buf[6*2+2+12+0],
                    parg->ta_prv[0].pa_buf[6*2+2+12+1],
                    parg->ta_prv[0].pa_buf[6*2+2+12+2],
                    parg->ta_prv[0].pa_buf[6*2+2+12+3],
                    parg->ta_prv[0].pa_buf[6*2+2+12+4+0],
                    parg->ta_prv[0].pa_buf[6*2+2+12+4+1],
                    parg->ta_prv[0].pa_buf[6*2+2+12+4+2],
                    parg->ta_prv[0].pa_buf[6*2+2+12+4+3]);
        } else if (parg->ta_prv[0].pa_buf[6*2]==0x08 &&
                parg->ta_prv[0].pa_buf[6*2+1]==0x06)
        {
            printf("  Type(2Bytes):%#02x%02x (ARP)\n",
                parg->ta_prv[0].pa_buf[6*2],
                parg->ta_prv[0].pa_buf[6*2+1]);
        }
        ;;
    }
    sem_post(&parg->ta_pub.pa_sem);
    pthread_exit(NULL);
    return NULL;
}

void *thread_send_handle(void *arg)
{
    thread_arg_t *parg = (thread_arg_t *)arg;

    /* 04. 将网络接口赋值给原始套接字地址结构 */
    struct sockaddr_ll sll;//原始套接字地址结构
    struct ifreq req;//网络接口地址
    strncpy(req.ifr_name, "ens33", IFNAMSIZ);//指定网卡名称
    if(-1 == ioctl(parg->ta_pub.pa_fd, SIOCGIFINDEX, &req))//获取网络接口
    {
        PRINT_POSITION_ERRNO("ioctl",errno);
        close(parg->ta_pub.pa_fd);
        exit(-1);
    }
    /*将网络接口赋值给原始套接字地址结构*/
    bzero(&sll, sizeof(sll));
    sll.sll_ifindex = req.ifr_ifindex;

    if(!sem_wait(&parg->ta_pub.pa_sem))
    {
        parg->ta_prv[1].pa_ret = 
            sendto(parg->ta_pub.pa_fd, parg->ta_prv[1].pa_buf, 
                    sizeof(parg->ta_prv[1].pa_buf), 0, (struct sockaddr *)&sll, sizeof(sll));
        if(parg->ta_prv[1].pa_ret == -1) {
            PRINT_POSITION_ERRNO("sendto",errno);
            close(parg->ta_pub.pa_fd);
            exit(-1);
        }
        printf(">>>> Send Network Packet Successfully !!!\n");
    }
    pthread_exit(NULL);
    return NULL;
}

